﻿namespace Whut.ObjectSharePointMapper.Internals
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.SharePoint;

    public class EntityHydrater
    {
        private MetaDataStore metaDataStore;

        private SessionLevelCache sessionLevelCache;

        public EntityHydrater(MetaDataStore metaDataStore, SessionLevelCache sessionLevelCache)
        {
            this.metaDataStore = metaDataStore;
            this.sessionLevelCache = sessionLevelCache;
        }

        public TEntity Hydrate<TEntity>(SPListItem item)
        {
            ListInfo listInfo = this.metaDataStore.GetListInfoFor<TEntity>();
            return this.Hydrate<TEntity>(listInfo, item);
        }

        public IEnumerable<TEntity> Hydrate<TEntity>(SPListItemCollection listItems)
        {
            ListInfo listInfo = this.metaDataStore.GetListInfoFor<TEntity>();
            foreach (SPListItem listItem in listItems)
            {
                yield return this.Hydrate<TEntity>(listInfo, listItem);
            }
        }

        public void UnHydrate<TEntity>(TEntity fromEntity, SPListItem toItem)
        {
            ListInfo listInfo = this.metaDataStore.GetListInfoFor<TEntity>();
            foreach (FieldInfo fieldInfo in listInfo.Fields)
            {
                SetSPListItemField<TEntity>(fromEntity, toItem, fieldInfo);
            }
        }

        public void ReHydrateLookups<TEntity>(TEntity entity, SPListItem item)
        {
            ListInfo listInfo = this.metaDataStore.GetListInfoFor<TEntity>();
            Type lookupType = typeof(Lookup);
            foreach (FieldInfo fieldInfo in listInfo.Fields.Where(c => c.PropertyType == lookupType))
            {
                SetEntityProperty(item, entity, fieldInfo);
            }
        }

        private TEntity Hydrate<TEntity>(ListInfo listInfo, SPListItem item)
        {
            TEntity entity = this.sessionLevelCache.TryFindEntity<TEntity>(item.ID);
            if (entity != null)
            {
                return entity;
            }

            entity = Activator.CreateInstance<TEntity>();
            listInfo.Id.PropertyInfo.SetValue(entity, item.ID, null);
            foreach (FieldInfo fieldInfo in listInfo.Fields)
            {
                SetEntityProperty(item, entity, fieldInfo);
            }

            this.sessionLevelCache.Store(item, entity);

            return entity;
        }

        private static void SetEntityProperty<TEntity>(SPListItem fromItem, TEntity entity, FieldInfo fieldInfo)
        {
            object value = TypeConverter.ToEntityPropertyValue(
                fieldInfo.PropertyType,
                fromItem[fieldInfo.FieldId]);
            fieldInfo.PropertyInfo.SetValue(entity, value, null);
        }

        private static void SetSPListItemField<TEntity>(TEntity fromEntity, SPListItem item, FieldInfo fieldInfo)
        {
            object value = TypeConverter.ToSPFieldValue(
                fieldInfo.PropertyType,
                fieldInfo.PropertyInfo.GetValue(fromEntity, null));
            // TODO don't update not changed fields
            item[fieldInfo.FieldId] = value;
        }
    }
}
